/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998, 1999 Alexander Larsson
 *
 * Custom Objects -- objects defined in XML rather than C.
 * Copyright (C) 1999 James Henstridge.
 *
 * Non-uniform scaling/subshape support by Marcel Toele.
 * Modifications (C) 2007 Kern Automatiseringsdiensten BV.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include <config.h>

#include <stdlib.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include <float.h>
#include <string.h>
#include "dia_xml_libxml.h"
#include "shape_info.h"
#include "custom_util.h"
#include "custom_object.h"
#include "dia_image.h"
#include "message.h"
#include "intl.h"
#include "prefs.h"

#include "units.h"

#define FONT_HEIGHT_DEFAULT 1
#define TEXT_ALIGNMENT_DEFAULT ALIGN_CENTER

static ShapeInfo *load_shape_info(const gchar *filename, ShapeInfo *preload);
static void update_bounds(ShapeInfo *info);

static GHashTable *name_to_info = NULL;

ShapeInfo *
shape_info_load(const gchar *filename)
{
  ShapeInfo *info = load_shape_info(filename, NULL);

  if (!info)
    return NULL;
  shape_info_register (info);
  return info;
}

void
shape_info_register (ShapeInfo *info)
{
  if (!name_to_info)
    name_to_info = g_hash_table_new(g_str_hash, g_str_equal);
  g_hash_table_insert(name_to_info, info->name, info);
}

ShapeInfo *
shape_info_get(ObjectNode obj_node)
{
  ShapeInfo *info = NULL;
  xmlChar *str;

  str = xmlGetProp(obj_node, (const xmlChar *)"type");
  if (str && name_to_info) {
    info = g_hash_table_lookup(name_to_info, (gchar *) str);
    if (!info->loaded)
      load_shape_info (info->filename, info);
    xmlFree(str);
  }
  return info;
}

ShapeInfo *
shape_info_getbyname(const gchar *name)
{
  if (name && name_to_info) {
    ShapeInfo *info = g_hash_table_lookup(name_to_info,name);
    if (!info->loaded)
      load_shape_info (info->filename, info);
    return info;
  }
  return NULL;
}

/**
 * shape_info_realise :
 * @info : the shape to realise
 * 
 * Puts the ShapeInfo into a form suitable for actual use (lazy loading)
 *
 */
void shape_info_realise(ShapeInfo* info)
{
  GList* tmp;
  
  for (tmp = info->display_list; tmp != NULL; tmp = tmp->next) {
    GraphicElement *el = tmp->data;
    if (el->type == GE_TEXT) {
          /* set default values for text style */
      if (!el->text.s.font_height)
        el->text.s.font_height = FONT_HEIGHT_DEFAULT;
      if (!el->text.s.font)
        el->text.s.font = dia_font_new_from_style(DIA_FONT_SANS,1.0);
      if (el->text.s.alignment == -1)
        el->text.s.alignment = TEXT_ALIGNMENT_DEFAULT;
      if (!el->text.object) {
        el->text.object = new_text(el->text.string,
                                   el->text.s.font,
                                   el->text.s.font_height,
                                   &el->text.anchor,
                                   &color_black,
                                   el->text.s.alignment);
      }
      text_calc_boundingbox(el->text.object, &el->text.text_bounds);
    }
  }  
}

real
shape_info_get_default_width(ShapeInfo *info)
{
  if (info->default_width == 0.0)
    info->default_width = DEFAULT_WIDTH * units[prefs_get_length_unit()].factor;
  
  return( info->default_width );
}

real
shape_info_get_default_height(ShapeInfo *info)
{
  if (info->default_height == 0.0)
    info->default_height = DEFAULT_HEIGHT * units[prefs_get_length_unit()].factor;
  
  return( info->default_height );
}

static void
parse_path(ShapeInfo *info, const char *path_str, DiaSvgStyle *s, const char* filename)
{
  GArray *points;
  gchar *pathdata = (gchar *)path_str, *unparsed;
  gboolean closed = FALSE;

  do {
    points = dia_svg_parse_path (pathdata, &unparsed, &closed);

    if (points->len > 0) {
      if (g_array_index(points, BezPoint, 0).type != BEZ_MOVE_TO) {
        message_warning (_("The file '%s' has invalid path data.\n"
	                   "svg:path data must start with moveto."),
			   dia_message_filename(filename));
      } else if (closed) {
        /* if there is some unclosed commands, add them as a GE_SHAPE */
	GraphicElementPath *el = g_malloc(sizeof(GraphicElementPath) +
					    points->len * sizeof(BezPoint));
	el->type = GE_SHAPE;
	dia_svg_style_init (&el->s, s);
	el->npoints = points->len;
	memcpy((char *)el->points, points->data, points->len*sizeof(BezPoint));
	info->display_list = g_list_append(info->display_list, el);
      } else {
        /* if there is some unclosed commands, add them as a GE_PATH */
	GraphicElementPath *el = g_malloc(sizeof(GraphicElementPath) +
				          points->len * sizeof(BezPoint));
	el->type = GE_PATH;
	dia_svg_style_init (&el->s, s);
	el->npoints = points->len;
	memcpy((char *)el->points, points->data, points->len*sizeof(BezPoint));
	info->display_list = g_list_append(info->display_list, el);
      }
      g_array_set_size (points, 0);
    }
    pathdata = unparsed;
    unparsed = NULL;
  } while (pathdata);

  g_array_free (points, TRUE);
}

static gboolean
is_subshape(xmlNode* node)
{
  gboolean res = FALSE;

  if (xmlHasProp(node, "subshape")) {
    gchar* value = xmlGetProp(node, "subshape");
    
    if (!strcmp(value, "true"))
      res = TRUE;
    
    xmlFree(value);
  }

  return res;
}

static void
parse_svg_node(ShapeInfo *info, xmlNodePtr node, xmlNsPtr svg_ns,
               DiaSvgStyle *style, const gchar *filename)
{
  xmlChar *str;

      /* walk SVG node ... */
  for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
    GraphicElement *el = NULL;
    DiaSvgStyle s;

    if (xmlIsBlankNode(node)) continue;
    if (node->type != XML_ELEMENT_NODE || node->ns != svg_ns)
      continue;
    dia_svg_style_init (&s, style);
    dia_svg_parse_style(node, &s, -1);
    if (!xmlStrcmp(node->name, (const xmlChar *)"line")) {
      GraphicElementLine *line = g_new0(GraphicElementLine, 1);

      el = (GraphicElement *)line;
      line->type = GE_LINE;
      str = xmlGetProp(node, (const xmlChar *)"x1");
      if (str) {
        line->p1.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"y1");
      if (str) {
        line->p1.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"x2");
      if (str) {
        line->p2.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"y2");
      if (str) {
        line->p2.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"polyline")) {
      GraphicElementPoly *poly;
      GArray *arr = g_array_new(FALSE, FALSE, sizeof(real));
      real val, *rarr;
      gchar *tmp;
      int i;

      str = xmlGetProp(node, (const xmlChar *)"points");
      tmp = (gchar *) str;
      while (tmp[0] != '\0') {
            /* skip junk */
        while (tmp[0] != '\0' && !g_ascii_isdigit(tmp[0]) && tmp[0]!='.'&&tmp[0]!='-')
          tmp++;
        if (tmp[0] == '\0') break;
        val = g_ascii_strtod(tmp, &tmp);
        g_array_append_val(arr, val);
      }
      xmlFree(str);
      val = 0;
      if (arr->len % 2 == 1) 
        g_array_append_val(arr, val);
      poly = g_malloc0(sizeof(GraphicElementPoly) + arr->len/2*sizeof(Point));
      el = (GraphicElement *)poly;
      poly->type = GE_POLYLINE;
      poly->npoints = arr->len / 2;
      rarr = (real *)arr->data;
      for (i = 0; i < poly->npoints; i++) {
        poly->points[i].x = rarr[2*i];
        poly->points[i].y = rarr[2*i+1];
      }
      g_array_free(arr, TRUE);
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"polygon")) {
      GraphicElementPoly *poly;
      GArray *arr = g_array_new(FALSE, FALSE, sizeof(real));
      real val, *rarr;
      char *tmp;
      int i;

      str = xmlGetProp(node, (const xmlChar *)"points");
      tmp = (char *) str;
      while (tmp[0] != '\0') {
            /* skip junk */
        while (tmp[0] != '\0' && !g_ascii_isdigit(tmp[0]) && tmp[0]!='.'&&tmp[0]!='-')
          tmp++;
        if (tmp[0] == '\0') break;
        val = g_ascii_strtod(tmp, &tmp);
        g_array_append_val(arr, val);
      }
      xmlFree(str);
      val = 0;
      if (arr->len % 2 == 1) 
        g_array_append_val(arr, val);
      poly = g_malloc0(sizeof(GraphicElementPoly) + arr->len/2*sizeof(Point));
      el = (GraphicElement *)poly;
      poly->type = GE_POLYGON;
      poly->npoints = arr->len / 2;
      rarr = (real *)arr->data;
      for (i = 0; i < poly->npoints; i++) {
        poly->points[i].x = rarr[2*i];
        poly->points[i].y = rarr[2*i+1];
      }
      g_array_free(arr, TRUE);
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"rect")) {
      GraphicElementRect *rect = g_new0(GraphicElementRect, 1);

      el = (GraphicElement *)rect;
      rect->type = GE_RECT;
      str = xmlGetProp(node, (const xmlChar *)"x");
      if (str) {
        rect->corner1.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      } else rect->corner1.x = 0;
      str = xmlGetProp(node, (const xmlChar *)"y");
      if (str) {
        rect->corner1.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      } else rect->corner1.y = 0;
      str = xmlGetProp(node, (const xmlChar *)"width");
      if (str) {
        rect->corner2.x = rect->corner1.x + g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"height");
      if (str) {
        rect->corner2.y = rect->corner1.y + g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"text")) {

      GraphicElementText *text = g_new(GraphicElementText, 1);
      el = (GraphicElement *)text;
      text->type = GE_TEXT;
      text->object = NULL;
      str = xmlGetProp(node, (const xmlChar *)"x");
      if (str) {
        text->anchor.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      } else text->anchor.x = 0;
      str = xmlGetProp(node, (const xmlChar *)"y");
      if (str) {
        text->anchor.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      } else text->anchor.y = 0;

      str = xmlNodeGetContent(node);
      if (str) {
        text->string = g_strdup((gchar *) str);
        xmlFree(str);
      } else text->string = g_strdup("");
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"circle")) {
      GraphicElementEllipse *ellipse = g_new0(GraphicElementEllipse, 1);

      el = (GraphicElement *)ellipse;
      ellipse->type = GE_ELLIPSE;
      str = xmlGetProp(node, (const xmlChar *)"cx");
      if (str) {
        ellipse->center.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"cy");
      if (str) {
        ellipse->center.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"r");
      if (str) {
        ellipse->width = ellipse->height = 2 * g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"ellipse")) {
      GraphicElementEllipse *ellipse = g_new0(GraphicElementEllipse, 1);

      el = (GraphicElement *)ellipse;
      ellipse->type = GE_ELLIPSE;
      str = xmlGetProp(node, (const xmlChar *)"cx");
      if (str) {
        ellipse->center.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"cy");
      if (str) {
        ellipse->center.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"rx");
      if (str) {
        ellipse->width = 2 * g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"ry");
      if (str) {
        ellipse->height = 2 * g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"path")) {
      str = xmlGetProp(node, (const xmlChar *)"d");
      if (str) {
        parse_path(info, (char *) str, &s, filename);
        xmlFree(str);
      }
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"image")) {
      GraphicElementImage *image = g_new0(GraphicElementImage, 1);

      el = (GraphicElement *)image;
      image->type = GE_IMAGE;
      str = xmlGetProp(node, (const xmlChar *)"x");
      if (str) {
        image->topleft.x = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"y");
      if (str) {
        image->topleft.y = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"width");
      if (str) {
        image->width = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"height");
      if (str) {
        image->height = g_ascii_strtod((gchar *) str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"xlink:href");
      if (!str) /* this doesn't look right but it appears to work w/o namespace --hb */
        str = xmlGetProp(node, (const xmlChar *)"href");
      if (str) {
        gchar *imgfn = g_filename_from_uri((gchar *) str, NULL, NULL);

        if (!imgfn)
	  /* despite it's name it ensures an absolute filename */
          imgfn = custom_get_relative_filename(filename, (gchar *) str);

        image->image = dia_image_load(imgfn);
        /* w/o the image we would crash later */
        if (!image->image) {
          g_warning("failed to load image file %s", imgfn ? imgfn : "(NULL)");
          image->image = dia_image_get_broken();
        }
        g_free(imgfn);
        xmlFree(str);
      }
    } else if (!xmlStrcmp(node->name, (const xmlChar *)"g")) {
      if (!is_subshape(node)) {
          /* add elements from the group element */
        parse_svg_node(info, node, svg_ns, &s, filename);
      } else {
          /* add elements from the group element, but make it a subshape */
        GraphicElementSubShape *subshape = g_new0(GraphicElementSubShape, 1);
        ShapeInfo* tmpinfo = g_new0(ShapeInfo, 1);
        DiaSvgStyle tmp_s = {
                1.0, DIA_SVG_COLOUR_FOREGROUND, 
                DIA_SVG_COLOUR_NONE,
                DIA_SVG_LINECAPS_DEFAULT, 
                DIA_SVG_LINEJOIN_DEFAULT,
                DIA_SVG_LINESTYLE_DEFAULT, 1.0
        };
        xmlChar *v_anchor_attr = xmlGetProp(node,"v_anchor");
        xmlChar *h_anchor_attr = xmlGetProp(node,"h_anchor");
      
        parse_svg_node(tmpinfo, node, svg_ns, &tmp_s, filename);
        
        tmpinfo->shape_bounds.top = DBL_MAX;
        tmpinfo->shape_bounds.left = DBL_MAX;
        tmpinfo->shape_bounds.bottom = -DBL_MAX;
        tmpinfo->shape_bounds.right = -DBL_MAX;

        update_bounds( tmpinfo );
        update_bounds( info );
        
        subshape->half_width = (tmpinfo->shape_bounds.right-tmpinfo->shape_bounds.left) / 2.0;
        subshape->half_height = (tmpinfo->shape_bounds.bottom-tmpinfo->shape_bounds.top) / 2.0;
        subshape->center.x = tmpinfo->shape_bounds.left + subshape->half_width;
        subshape->center.y = tmpinfo->shape_bounds.top + subshape->half_height;
            
        subshape->type = GE_SUBSHAPE;
        subshape->display_list = tmpinfo->display_list;
        subshape->v_anchor_method = OFFSET_METHOD_FIXED;
        subshape->h_anchor_method = OFFSET_METHOD_FIXED;
        subshape->default_scale = 0.0;
                
        if (!v_anchor_attr || !strcmp(v_anchor_attr,"fixed.top"))
          subshape->v_anchor_method = OFFSET_METHOD_FIXED;
        else if (v_anchor_attr && !strcmp(v_anchor_attr,"fixed.bottom"))
          subshape->v_anchor_method = -OFFSET_METHOD_FIXED;
        else if (v_anchor_attr && !strcmp(v_anchor_attr,"proportional"))
          subshape->v_anchor_method = OFFSET_METHOD_PROPORTIONAL;
        else
          fprintf( stderr, "illegal v_anchor `%s', defaulting to fixed.top\n",
                   v_anchor_attr );
          
        if (!h_anchor_attr || !strcmp(h_anchor_attr,"fixed.left"))
          subshape->h_anchor_method = OFFSET_METHOD_FIXED;
        else if (h_anchor_attr && !strcmp(h_anchor_attr,"fixed.right"))
          subshape->h_anchor_method = -OFFSET_METHOD_FIXED;
        else if (h_anchor_attr && !strcmp(h_anchor_attr,"proportional"))
          subshape->h_anchor_method = OFFSET_METHOD_PROPORTIONAL;
        else
          fprintf( stderr, "illegal h_anchor `%s', defaulting to fixed.left\n",
                   h_anchor_attr );
        
        info->subshapes = g_list_append(info->subshapes, subshape);
        
        /* gfree( tmpinfo );*/
        xmlFree(v_anchor_attr);
        xmlFree(h_anchor_attr);
        
        el = (GraphicElementSubShape *)subshape;
      }    
    }
    if (el) {
      el->any.s = s;
      if (el->any.s.font)
        el->any.s.font = g_object_ref(s.font);
      info->display_list = g_list_append(info->display_list, el);
    }
    if (s.font)
      dia_font_unref (s.font);
  }
}

static void
check_point(ShapeInfo *info, Point *pt)
{
  if (pt->x < info->shape_bounds.left)
    info->shape_bounds.left = pt->x;
  if (pt->x > info->shape_bounds.right)
    info->shape_bounds.right = pt->x;
  if (pt->y < info->shape_bounds.top)
    info->shape_bounds.top = pt->y;
  if (pt->y > info->shape_bounds.bottom)
    info->shape_bounds.bottom = pt->y;
}

static void
update_bounds(ShapeInfo *info)
{
  GList *tmp;
  Point pt;
  for (tmp = info->display_list; tmp; tmp = tmp->next) {
    GraphicElement *el = tmp->data;
    int i;

    switch (el->type) {
    case GE_LINE:
      check_point(info, &(el->line.p1));
      check_point(info, &(el->line.p2));
      break;
    case GE_POLYLINE:
      for (i = 0; i < el->polyline.npoints; i++)
	check_point(info, &(el->polyline.points[i]));
      break;
    case GE_POLYGON:
      for (i = 0; i < el->polygon.npoints; i++)
	check_point(info, &(el->polygon.points[i]));
      break;
    case GE_RECT:
      check_point(info, &(el->rect.corner1));
      check_point(info, &(el->rect.corner2));
      break;
    case GE_TEXT:
      check_point(info, &(el->text.anchor));
      break;
    case GE_ELLIPSE:
      pt = el->ellipse.center;
      pt.x -= el->ellipse.width / 2.0;
      pt.y -= el->ellipse.height / 2.0;
      check_point(info, &pt);
      pt.x += el->ellipse.width;
      pt.y += el->ellipse.height;
      check_point(info, &pt);
      break;
    case GE_PATH:
    case GE_SHAPE:
      for (i = 0; i < el->path.npoints; i++)
	switch (el->path.points[i].type) {
	case BEZ_CURVE_TO:
	  check_point(info, &el->path.points[i].p3);
	  check_point(info, &el->path.points[i].p2);
	case BEZ_MOVE_TO:
	case BEZ_LINE_TO:
	  check_point(info, &el->path.points[i].p1);
	}
      break;
    case GE_IMAGE:
      check_point(info, &(el->image.topleft));
      pt.x = el->image.topleft.x + el->image.width;
      pt.y = el->image.topleft.y + el->image.height;
      check_point(info, &pt);
      break;
    }
  }
  
  {
    real width = info->shape_bounds.right-info->shape_bounds.left;
    real height = info->shape_bounds.bottom-info->shape_bounds.top;
    
    if (info->default_width > 0.0 && info->default_height == 0.0) {
      info->default_height = (info->default_width / width) * height;
    } else if (info->default_height > 0.0 && info->default_width == 0.0) {
      info->default_width = (info->default_height / height) * width;      
    }
  }
}

static ShapeInfo *
load_shape_info(const gchar *filename, ShapeInfo *preload)
{
  xmlDocPtr doc = xmlDoParseFile(filename);
  xmlNsPtr shape_ns, svg_ns;
  xmlNodePtr node, root, ext_node = NULL;
  ShapeInfo *info;
  gchar *tmp;
  int i;
  
  if (!doc) {
    g_warning("parse error for %s", filename);
    return NULL;
  }
  /* skip (emacs) comments */
  root = doc->xmlRootNode;
  while (root && (root->type != XML_ELEMENT_NODE)) root = root->next;
  if (!root) return NULL;
  if (xmlIsBlankNode(root)) return NULL;

  if (!(shape_ns = xmlSearchNsByHref(doc, root,
		(const xmlChar *)"http://www.daa.com.au/~james/dia-shape-ns"))) {
    xmlFreeDoc(doc);
    g_warning("could not find shape namespace");
    return NULL;
  }
  if (!(svg_ns = xmlSearchNsByHref(doc, root, (const xmlChar *)"http://www.w3.org/2000/svg"))) {
    xmlFreeDoc(doc);
    g_warning("could not find svg namespace");
    return NULL;
  }
  if (root->ns != shape_ns || xmlStrcmp(root->name, (const xmlChar *)"shape")) {
    g_warning("root element was %s -- expecting shape", root->name);
    xmlFreeDoc(doc);
    return NULL;
  }

  if (preload)
    info = preload;
  else 
    info = g_new0(ShapeInfo, 1);
  info->loaded = TRUE;
  info->shape_bounds.top = DBL_MAX;
  info->shape_bounds.left = DBL_MAX;
  info->shape_bounds.bottom = -DBL_MAX;
  info->shape_bounds.right = -DBL_MAX;
  info->aspect_type = SHAPE_ASPECT_FREE; 
  info->default_width = 0.0;
  info->default_height = 0.0;
  info->main_cp = -1;

  i = 0;
  for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
    if (xmlIsBlankNode(node)) continue;
    if (node->type != XML_ELEMENT_NODE) continue;
    if (node->ns == shape_ns && !xmlStrcmp(node->name, (const xmlChar *)"name")) {
      tmp = (gchar *) xmlNodeGetContent(node);
      if (preload) { 
        if (strcmp (tmp, info->name) != 0)
          g_warning ("Shape(preload) '%s' can't change name '%s'", info->name, tmp);
        /* the key name is already used as key in name_to_info */
      } else {
        g_free(info->name);
        info->name = g_strdup(tmp);
      }
      xmlFree(tmp);
    } else if (node->ns == shape_ns && !xmlStrcmp(node->name, (const xmlChar *)"icon")) {
      tmp = (gchar *) xmlNodeGetContent(node);
      if (preload) { 
        if (strstr (info->icon, tmp) == NULL) /* the left including the absolute path */
          g_warning ("Shape(preload) '%s' can't change icon '%s'", info->icon, tmp);
        /* the key name is already used as key in name_to_info */
      } else {
        g_free(info->icon);
        info->icon = custom_get_relative_filename(filename, tmp);
      }
      xmlFree(tmp);
    } else if (node->ns == shape_ns && !xmlStrcmp(node->name, (const xmlChar *)"connections")) {
      GArray *arr = g_array_new(FALSE, FALSE, sizeof(Point));
      xmlNodePtr pt_node;

      for (pt_node = node->xmlChildrenNode; 
           pt_node != NULL; 
           pt_node = pt_node->next) {
        if (xmlIsBlankNode(pt_node)) continue;

	if (pt_node->ns == shape_ns && !xmlStrcmp(pt_node->name, (const xmlChar *)"point")) {
	  Point pt = { 0.0, 0.0 };
	  xmlChar *str;

	  str = xmlGetProp(pt_node, (const xmlChar *)"x");
	  if (str) {
	    pt.x = g_ascii_strtod((gchar *) str, NULL);
	    xmlFree(str);
	  }
	  str = xmlGetProp(pt_node, (const xmlChar *)"y");
	  if (str) {
	    pt.y = g_ascii_strtod((gchar *) str, NULL);
	    xmlFree(str);
	  }
	  g_array_append_val(arr, pt);
	  str = xmlGetProp(pt_node, (const xmlChar *)"main");
	  if (str && str[0] != '\0') {
	    if (info->main_cp != -1) {
	      message_warning("More than one main connection point in %s.  Only the first one will be used.\n",
			      info->name);
	    } else {
	      info->main_cp = i;
	    }
	    xmlFree(str);
	  }
	}
	i++;
      }
      info->nconnections = arr->len;
      info->connections = (Point *)arr->data;
      g_array_free(arr, FALSE);
    } else if (node->ns == shape_ns && !xmlStrcmp(node->name, (const xmlChar *)"textbox")) {
      xmlChar *str;
      
      str = xmlGetProp(node, (const xmlChar *)"x1");
      if (str) {
	info->text_bounds.left = g_ascii_strtod((gchar *) str, NULL);
	xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"y1");
      if (str) {
	info->text_bounds.top = g_ascii_strtod((gchar *) str, NULL);
	xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"x2");
      if (str) {
	info->text_bounds.right = g_ascii_strtod((gchar *) str, NULL);
	xmlFree(str);
      }
      str = xmlGetProp(node, (const xmlChar *)"y2");
      if (str) {
	info->text_bounds.bottom = g_ascii_strtod((gchar *) str, NULL);
	xmlFree(str);
      }
      info->resize_with_text = TRUE;
      str = xmlGetProp(node, (const xmlChar *)"resize");
      if (str) {
        info->resize_with_text = TRUE;
        if (!xmlStrcmp(str,(const xmlChar *)"no"))
          info->resize_with_text = FALSE;
	xmlFree(str);
      }
      info->text_align = ALIGN_CENTER;
      str = xmlGetProp(node, (const xmlChar *)"align");
      if (str) {
	if (!xmlStrcmp(str, (const xmlChar *)"left")) 
	  info->text_align = ALIGN_LEFT;
	else if (!xmlStrcmp(str, (const xmlChar *)"right"))
	  info->text_align = ALIGN_RIGHT;
	xmlFree(str);
      }
      info->has_text = TRUE;
    } else if (node->ns == shape_ns && !xmlStrcmp(node->name, (const xmlChar *)"aspectratio")) {
      tmp = (gchar *) xmlGetProp(node, (const xmlChar *)"type");
      if (tmp) {
	if (!strcmp(tmp, "free"))
	  info->aspect_type = SHAPE_ASPECT_FREE;
	else if (!strcmp(tmp, "fixed"))
	  info->aspect_type = SHAPE_ASPECT_FIXED;
	else if (!strcmp(tmp, "range")) {
	  xmlChar *str;

	  info->aspect_type = SHAPE_ASPECT_RANGE;
	  info->aspect_min = 0.0;
	  info->aspect_max = G_MAXFLOAT;
	  str = xmlGetProp(node, (const xmlChar *)"min");
	  if (str) {
	    info->aspect_min = g_ascii_strtod((gchar *) str, NULL);
	    xmlFree(str);
	  }
	  str = xmlGetProp(node, (const xmlChar *)"max");
	  if (str) {
	    info->aspect_max = g_ascii_strtod((gchar *) str, NULL);
	    xmlFree(str);
	  }
	  if (info->aspect_max < info->aspect_min) {
	    real asp = info->aspect_max;
	    info->aspect_max = info->aspect_min;
	    info->aspect_min = asp;
	  }
	}
	xmlFree(tmp);
      }
    } else if (node->ns == shape_ns && (!xmlStrcmp(node->name, (const xmlChar *)"default-width") || !xmlStrcmp(node->name, (const xmlChar *)"default-height"))) {
      
      int j = 0;
      DiaUnitDef ud;
      
      gdouble val = 0.0;
      
      int unit_ssize = 0;
      int ssize = 0;
      tmp = (gchar *) xmlNodeGetContent(node);
      ssize = strlen(tmp);

      val = g_ascii_strtod(tmp, NULL);

      for (j = 0, ud = units[i]; ud.name; ud = units[++j]) {
        unit_ssize = strlen(ud.unit);
        if (ssize > unit_ssize && !strcmp(tmp+(ssize-unit_ssize), ud.unit)) {
          val *= ud.factor;
          break;
        }
      }

      if (!xmlStrcmp(node->name, (const xmlChar *)"default-width")) {
        info->default_width = val;
      } else {
        info->default_height = val;
      }
      
      xmlFree(tmp);
    } else if (node->ns == svg_ns && !xmlStrcmp(node->name, (const xmlChar *)"svg")) {
      DiaSvgStyle s = {
	1.0, DIA_SVG_COLOUR_FOREGROUND, DIA_SVG_COLOUR_NONE,
	DIA_SVG_LINECAPS_DEFAULT, DIA_SVG_LINEJOIN_DEFAULT, DIA_SVG_LINESTYLE_DEFAULT, 1.0
      };

      dia_svg_parse_style(node, &s, -1);
      parse_svg_node(info, node, svg_ns, &s, filename);
      update_bounds(info);
    }
	else if (!xmlStrcmp(node->name, (const xmlChar *)"ext_attributes")) {
      ext_node = node;
    }
  }
  /*MC 11/03 parse ext attributes if any & prepare prop tables */
  custom_setup_properties (info, ext_node);
  xmlFreeDoc(doc);
  return info;
}

void
shape_info_print(ShapeInfo *info)
{
  GList *tmp;
  int i;

  g_print("Name        : %s\n", info->name);
  g_print("Connections :\n");
  for (i = 0; i < info->nconnections; i++)
    g_print("  (%g, %g)\n", info->connections[i].x, info->connections[i].y);
  g_print("Shape bounds: (%g, %g) - (%g, %g)\n",
	  info->shape_bounds.left, info->shape_bounds.top,
	  info->shape_bounds.right, info->shape_bounds.bottom);
  if (info->has_text)
    g_print("Text bounds : (%g, %g) - (%g, %g)\n",
	    info->text_bounds.left, info->text_bounds.top,
	    info->text_bounds.right, info->text_bounds.bottom);
  g_print("Aspect ratio: ");
  switch (info->aspect_type) {
  case SHAPE_ASPECT_FREE: g_print("free\n"); break;
  case SHAPE_ASPECT_FIXED: g_print("fixed\n"); break;
  case SHAPE_ASPECT_RANGE:
    g_print("range %g - %g\n", info->aspect_min, info->aspect_max); break;
  }
  g_print("Display list:\n");
  for (tmp = info->display_list; tmp; tmp = tmp->next) {
    GraphicElement *el = tmp->data;
    int i;

    switch (el->type) {
    case GE_LINE:
      g_print("  line: (%g, %g) (%g, %g)\n", el->line.p1.x, el->line.p1.y,
	      el->line.p2.x, el->line.p2.y);
      break;
    case GE_POLYLINE:
      g_print("  polyline:");
      for (i = 0; i < el->polyline.npoints; i++)
	g_print(" (%g, %g)", el->polyline.points[i].x,
		el->polyline.points[i].y);
      g_print("\n");
      break;
    case GE_POLYGON:
      g_print("  polygon:");
      for (i = 0; i < el->polygon.npoints; i++)
	g_print(" (%g, %g)", el->polygon.points[i].x,
		el->polygon.points[i].y);
      g_print("\n");
      break;
    case GE_RECT:
      g_print("  rect: (%g, %g) (%g, %g)\n",
	      el->rect.corner1.x, el->rect.corner1.y,
	      el->rect.corner2.x, el->rect.corner2.y);
      break;
    case GE_TEXT:
      g_print("  text: (%g, %g)\n",
	      el->text.anchor.x, el->text.anchor.y);
      break;
    case GE_ELLIPSE:
      g_print("  ellipse: center=(%g, %g) width=%g height=%g\n",
	      el->ellipse.center.x, el->ellipse.center.y,
	      el->ellipse.width, el->ellipse.height);
      break;
    case GE_PATH:
      g_print("  path:");
      for (i = 0; i < el->path.npoints; i++)
	switch (el->path.points[i].type) {
	case BEZ_MOVE_TO:
	  g_print(" M (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_LINE_TO:
	  g_print(" L (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_CURVE_TO:
	  g_print(" C (%g, %g) (%g, %g) (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y, el->path.points[i].p2.x,
		  el->path.points[i].p2.y, el->path.points[i].p3.x,
		  el->path.points[i].p3.y);
	  break;
	}
      break;
    case GE_SHAPE:
      g_print("  shape:");
      for (i = 0; i < el->path.npoints; i++)
	switch (el->path.points[i].type) {
	case BEZ_MOVE_TO:
	  g_print(" M (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_LINE_TO:
	  g_print(" L (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_CURVE_TO:
	  g_print(" C (%g, %g) (%g, %g) (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y, el->path.points[i].p2.x,
		  el->path.points[i].p2.y, el->path.points[i].p3.x,
		  el->path.points[i].p3.y);
	  break;
	}
      break;
    case GE_IMAGE :
      g_print("  image topleft=(%g, %g) width=%g height=%g file=%s\n",
              el->image.topleft.x, el->image.topleft.y,
              el->image.width, el->image.height,
              el->image.image ? dia_image_filename(el->image.image) : "(nil)");
      break;
    default:
      break;
    }
  }
  g_print("\n");
}
